package me.lwn.auth.security.web.authentication;

import me.lwn.auth.domain.LockedRecord;
import me.lwn.auth.domain.LoginRecord;
import me.lwn.auth.infrastructure.repository.LockedRecordRepository;
import me.lwn.auth.infrastructure.repository.LoginRecordRepository;
import me.lwn.auth.security.utils.HttpUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;

@Component
public class RedisLoginLockedService extends LoginLockedSupport {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ApplicationContext context;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    public RedisLoginLockedService(RedisTemplate<String, Object> redisTemplate, ApplicationContext context) {
        this.redisTemplate = redisTemplate;
        this.context = context;
    }

    public Integer get(HttpServletRequest request) {
        Integer res = (Integer) redisTemplate.opsForValue().get(getKey(request));
        return res != null ? res : 0;
    }

    @Override
    protected void doCount(HttpServletRequest request, HttpServletResponse response, String username, String message, int times, int duration) {

        String key = getKey(request);

        List<Object> results = redisTemplate.execute(new SessionCallback<List<Object>>() {
            @SuppressWarnings({"unchecked"})
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                LocalDateTime midnight = LocalDateTime.ofInstant(new Date().toInstant(),
                                ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
                        .withSecond(0).withNano(0);
                LocalDateTime currentDateTime = LocalDateTime.ofInstant(new Date().toInstant(),
                        ZoneId.systemDefault());
                operations.multi();
                operations.opsForValue().setIfAbsent(key, 0,
                        Duration.ofMinutes(ChronoUnit.MINUTES.between(currentDateTime, midnight)));
                operations.opsForValue().increment(key);
                return operations.exec();
            }
        });

        if (!CollectionUtils.isEmpty(results) && results.size() >= 2 && (Long) (results.get(1)) >= times) {

            redisTemplate.expire(key, Duration.ofSeconds(duration));
            String clientId = getClientId(request);
            String ip = HttpUtils.getIpAddress(request);
            //登陆锁定日志
            lockedRecord(clientId, ip, username, duration);
        }
    }

    @Override
    protected RegisteredClient getRegisteredClient(String clientId) {
        RegisteredClientRepository repository = context.getBean(RegisteredClientRepository.class);
        RegisteredClient client = repository.findByClientId(clientId);
        if (client == null) {
            throw new OAuth2AuthorizationException(new OAuth2Error("", "", ""), "");
        }
        return client;
    }

    protected void loginRecord(String clientId, String ip, String username, String reason, boolean status) {

        LoginRecordRepository repository = context.getBean(LoginRecordRepository.class);
        LoginRecord record = new LoginRecord();
        record.setId(UUID.randomUUID().toString());
        record.setUsername(username);
        record.setClientId(clientId);
        record.setIpHost(ip);
        record.setLoginTime(LocalDateTime.now());
        record.setMessage(reason);
        record.setStatus(status);
        repository.save(record);
    }

    protected void lockedRecord(String clientId, String ip, String username, long duration) {
        //记录锁定记录
        LocalDateTime expireDate = LocalDateTime.now().plus(Duration.ofMinutes(duration));
        LockedRecordRepository repository = context.getBean(LockedRecordRepository.class);
        LockedRecord record = new LockedRecord();
        record.setId(UUID.randomUUID().toString());
        record.setUsername(username);
        record.setClientId(clientId);
        record.setIpHost(ip);
        record.setLockedTime(LocalDateTime.now());
        record.setExpireTime(expireDate);
        record.setReleaseStatus(expireDate);
        repository.save(record);
    }

    public void remove(HttpServletRequest request, String username, String message) {
        String clientId = getClientId(request);
        String ip = HttpUtils.getIpAddress(request);
        redisTemplate.delete(getKey(request));
        loginRecord(clientId, ip, username, message, true);
    }

    public void remove(String clientId, String ip, String username) {
        redisTemplate.delete(getKey(clientId, ip, username));
    }

    public void removeAll() {
        Set<String> keys = redisTemplate.keys(getIpLockedPrefix() + "*");
        if (!CollectionUtils.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
        keys = redisTemplate.keys(getUserLockedPrefix() + "*");
        if (!CollectionUtils.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }
}